﻿#region namespaces

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Windows;
using ZOIL.DataModel;

#endregion

namespace ZOIL.DataBackend.BaseX
{
    //TODO: IList implementieren?
    public sealed class BaseXCollection : IPersistableCollection
    {
        //TODO: noch methoden um remote zu loaden und nur local zu entfernen

        #region Data

        private readonly ObservableHashSet<ItemPair> _items;

        //TODO: Vergleich anpassen: Raus normalen Tupel-Vergleich geben, intern Referenzgleichheit der Elemente verwenden
        internal sealed class ItemPair : Tuple<IPersistable, IVisualProperties>, IEquatable<ItemPair>
        {
            public ItemPair(IPersistable obj, IVisualProperties prop)
                : base(obj, prop)
            {
            }

            public IPersistable DatabasePersistable
            {
                get { return Item1; }
            }

            public IVisualProperties VisualProperties
            {
                get { return Item2; }
            }

            public long Id
            {
                get { return DatabasePersistable.DatabaseId; }
            }

            #region IEquatable<ItemPair> Members

            public bool Equals(ItemPair other)
            {
                if (other == null)
                {
                    return false;
                }

                return Id == other.Id && ReferenceEquals(DatabasePersistable, other.DatabasePersistable) &&
                       ReferenceEquals(VisualProperties, other.VisualProperties);
            }

            #endregion

            public override bool Equals(object obj)
            {
                if (ReferenceEquals(null, obj))
                {
                    return false;
                }
                if (ReferenceEquals(this, obj))
                {
                    return true;
                }
                if (obj.GetType() != GetType())
                {
                    return false;
                }
                return Equals((ItemPair)obj);
            }

            public override int GetHashCode()
            {
                //                return Id.GetHashCode();
                return base.GetHashCode();
            }

            public static ItemPair Create(IPersistable obj, IVisualProperties prop)
            {
                return new ItemPair(obj, prop);
            }

            public static ItemPair FromTuple(Tuple<IPersistable, IVisualProperties> tuple)
            {
                var ci = tuple as ItemPair;
                if (ci != null)
                {
                    return ci;
                }

                return new ItemPair(tuple.Item1, tuple.Item2);
            }

            public static ItemPair FromDatabaseObjectActionEventArgs(DatabaseObjectActionEventArgs e)
            {
                return new ItemPair(e.DatabasePersistable, e.VisualProperties);
            }
        }

        #endregion

        #region fields

        private readonly Database _db;
        private readonly ReaderWriterLockSlim _rwl = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);

        #endregion

        #region ctor

        public BaseXCollection(DatabaseConfiguration dbConfig, string initialLoadXQuery)
        {
            if (dbConfig == null)
            {
                throw new ArgumentNullException("dbConfig");
            }

            ViewFor.AddViewFor(typeof(BaseXCollection), new Dictionary<string, Type> { { "default", typeof(PersistableCollectionView) } });

            //Verbinden
            _db = dbConfig.ConnectToDatabase();

            //Items holen wenn query angegeben
            IEnumerable<ItemPair> initialItems = Enumerable.Empty<ItemPair>();
            if (!string.IsNullOrWhiteSpace(initialLoadXQuery))
            {
                initialItems = _db.Load(initialLoadXQuery).Select(ItemPair.FromTuple);
            }
            _items = new ObservableHashSet<ItemPair>(initialItems);

            _db.ObjectRemotelyCreated += OnObjectRemotelyCreated;
            _db.ObjectRemotelyDeleted += OnObjectRemotelyDeleted;

            //            _items.CollectionChanged += (sender, args) => Debug.WriteLine("----Action: {0}", args.Action);
        }

        #endregion

        #region Handle remote events

        private void OnObjectRemotelyCreated(object sender, DatabaseObjectActionEventArgs e)
        {
            Debug.WriteLine("Got add on {0}", e.DatabasePersistable.DatabaseId);
            AddLocal(ItemPair.FromDatabaseObjectActionEventArgs(e));
        }

        private void OnObjectRemotelyDeleted(object sender, DatabaseObjectActionEventArgs e)
        {
            Debug.WriteLine("Got remove on {0}", e.DatabasePersistable.DatabaseId);
            RemoveLocal(ItemPair.FromDatabaseObjectActionEventArgs(e));
        }

        #endregion

        #region Functions without db

        private void AddLocal(ItemPair item)
        {
            //bei unit-test: keine Applicaton.Current
            if (Application.Current == null)
            {
                using (_rwl.WriteLock())
                {
                    _items.Add(item);
                }
            }
            else
            {
                Application.Current.Dispatcher.BeginInvoke(new Action(() =>
                    {
                        using (_rwl.WriteLock())
                        {
                            _items.Add(item);
                        }
                    }));
            }

            //            using (_rwl.WriteLock())
            //            {
            //                _items.Add(item);
            //            }
        }

        private void RemoveLocal(ItemPair item)
        {
            //bei unit-test: keine Applicaton.Current
            if (Application.Current == null)
            {
                using (_rwl.WriteLock())
                {
                    _items.Remove(item);
                }
            }
            else
            {
                Application.Current.Dispatcher.BeginInvoke(new Action(() =>
                    {
                        using (_rwl.WriteLock())
                        {
                            _items.Remove(item);
                        }
                    }));
            }

            //            using (_rwl.WriteLock())
            //            {
            //                _items.Remove(item);
            //            }
        }

        #endregion

        #region Load Remote

        /// <summary>
        ///     Loads elements via the passed <paramref name="xquery" /> from the database, inserts them into this collection and returns the new elements
        ///     If a loaded element is already present in this collection it will not be added again, but the existing one will be returned
        /// </summary>
        public IEnumerable<Tuple<IPersistable, IVisualProperties>> AddFromDatabase(string xquery)
        {
            //laden kann ohne locken passieren
            var es = _db.Load(xquery).Select(ItemPair.FromTuple);

            var l = es as List<ItemPair> ?? es.ToList();
            using (_rwl.WriteLock())
            {
                foreach (var e in l)
                {
                    if (!Contains(e))
                    {
                        AddLocal(e);
                    }
                }
            }
            return l;
        }

        #endregion

        #region Indexer

        public Tuple<IPersistable, IVisualProperties> this[long id]
        {
            get { return Get(id); }
        }

        public Tuple<IPersistable, IVisualProperties> Get(long id)
        {
            using (_rwl.ReadLock())
            {
                return _items.Single(ip => ip.Id == id);
            }
        }

        #endregion

        #region Implementation of IEnumerable

        /// <summary>
        ///     Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        ///     A <see cref="T:System.Collections.Generic.IEnumerator`1" /> that can be used to iterate through the collection.
        /// </returns>
        /// <filterpriority>1</filterpriority>
        public IEnumerator<Tuple<IPersistable, IVisualProperties>> GetEnumerator()
        {
            //TODO: locken
            return _items.GetEnumerator();
        }

        /// <summary>
        ///     Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        ///     An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        #region Implementation of ICollection<Tuple<IDatabaseObject,IVisualProperties>>

        /// <summary>
        ///     Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1" />.
        /// </summary>
        /// <param name="item">
        ///     The object to add to the <see cref="T:System.Collections.Generic.ICollection`1" />.
        /// </param>
        /// <exception cref="T:System.NotSupportedException">
        ///     The <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only.
        /// </exception>
        void ICollection<Tuple<IPersistable, IVisualProperties>>.Add(Tuple<IPersistable, IVisualProperties> item)
        {
            Add(item);
        }

        /// <summary>
        ///     Adds an element to the current set and returns a value to indicate if the element was successfully added.
        /// </summary>
        /// <returns>
        ///     true if the element is added to the set; false if the element is already in the set.
        /// </returns>
        /// <param name="item">The element to add to the set.</param>
        public bool Add(Tuple<IPersistable, IVisualProperties> item)
        {
            using (_rwl.WriteLock())
            {
                var i = ItemPair.FromTuple(item);

                //schon in hashset?
                if (_items.Contains(i))
                {
                    return false;
                }

                //darf noch nicht in db sein
                if (i.Id != default(long) || i.DatabasePersistable.DatabaseId != default(long) ||
                    i.VisualProperties.BelongsToDatabaseId != default(long))
                {
                    throw new ArgumentException(string.Format("Item is already stored in database (ID {0})", i.Id), "item");
                }

                //erst remote hinzufügen
                _db.Store(i.DatabasePersistable, i.VisualProperties);

                //dann lokal speichern
                AddLocal(i);
                return true;
            }
        }

        /// <summary>
        ///     Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1" />.
        /// </summary>
        /// <exception cref="T:System.NotSupportedException">
        ///     The
        ///     <see cref="T:System.Collections.Generic.ICollection`1" />
        ///     is read-only.
        /// </exception>
        public void Clear()
        {
            using (_rwl.ReadLock())
            {
                //erst alle aus db löschen
                foreach (var e in _items)
                {
                    _db.Delete(e.Id);
                }
                _items.Clear();
            }
        }

        /// <summary>
        ///     Determines whether the <see cref="T:System.Collections.Generic.ICollection`1" /> contains a specific value.
        /// </summary>
        /// <returns>
        ///     true if <paramref name="item" /> is found in the <see cref="T:System.Collections.Generic.ICollection`1" /> ; otherwise, false.
        /// </returns>
        /// <param name="item">
        ///     The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1" /> .
        /// </param>
        public bool Contains(Tuple<IPersistable, IVisualProperties> item)
        {
            using (_rwl.ReadLock())
            {
                return _items.Contains(ItemPair.FromTuple(item));
            }
        }

        /// <summary>
        ///     Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1" /> to an
        ///     <see
        ///         cref="T:System.Array" />
        ///     , starting at a particular
        ///     <see
        ///         cref="T:System.Array" />
        ///     index.
        /// </summary>
        /// <param name="array">
        ///     The one-dimensional <see cref="T:System.Array" /> that is the destination of the elements copied from
        ///     <see
        ///         cref="T:System.Collections.Generic.ICollection`1" />
        ///     . The <see cref="T:System.Array" /> must have zero-based indexing.
        /// </param>
        /// <param name="arrayIndex">
        ///     The zero-based index in <paramref name="array" /> at which copying begins.
        /// </param>
        /// <exception cref="T:System.ArgumentNullException">
        ///     <paramref name="array" />
        ///     is null.
        /// </exception>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        ///     <paramref name="arrayIndex" />
        ///     is less than 0.
        /// </exception>
        /// <exception cref="T:System.ArgumentException">
        ///     The number of elements in the source
        ///     <see cref="T:System.Collections.Generic.ICollection`1" />
        ///     is greater than the available space from
        ///     <paramref name="arrayIndex" />
        ///     to the end of the destination
        ///     <paramref name="array" />
        ///     .
        /// </exception>
        public void CopyTo(Tuple<IPersistable, IVisualProperties>[] array, int arrayIndex)
        {
            using (_rwl.ReadLock())
            {
                //TODO: kann man des nicht auch direkt machen?
                _items.Cast<Tuple<IPersistable, IVisualProperties>>().ToArray().CopyTo(array, arrayIndex);
            }
        }

        /// <summary>
        ///     Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1" />.
        /// </summary>
        /// <returns>
        ///     true if <paramref name="item" /> was successfully removed from the
        ///     <see
        ///         cref="T:System.Collections.Generic.ICollection`1" />
        ///     ; otherwise, false. This method also returns false if
        ///     <paramref
        ///         name="item" />
        ///     is not found in the original <see cref="T:System.Collections.Generic.ICollection`1" /> .
        /// </returns>
        /// <param name="item">
        ///     The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1" /> .
        /// </param>
        /// <exception cref="T:System.NotSupportedException">
        ///     The
        ///     <see cref="T:System.Collections.Generic.ICollection`1" />
        ///     is read-only.
        /// </exception>
        public bool Remove(Tuple<IPersistable, IVisualProperties> item)
        {
            using (_rwl.WriteLock())
            {
                var e = ItemPair.FromTuple(item);

                if (!_items.Remove(e))
                {
                    return false;
                }

                //erst aus db entfernen
                _db.Delete(e.Id);
                _items.Remove(e);
                return true;
            }
        }

        public string BackendName { get { return "BaseX"; } }

        public bool Remove(long id)
        {
            var item = GetPersistableAndVisualPropertiesIfExisting(id);
            return item != null && Remove(item);
        }

        /// <summary>
        ///     Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1" />.
        /// </summary>
        /// <returns>
        ///     The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1" /> .
        /// </returns>
        public int Count
        {
            get
            {
                using (_rwl.ReadLock())
                {
                    return _items.Count;
                }
            }
        }

        /// <summary>
        ///     Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only.
        /// </summary>
        /// <returns>
        ///     true if the <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only; otherwise, false.
        /// </returns>
        public bool IsReadOnly
        {
            get { return false; }
        }

        #endregion

        #region Implementation of INotifyCollectionChanged

        public event NotifyCollectionChangedEventHandler CollectionChanged
        {
            add { _items.CollectionChanged += value; }
            remove { _items.CollectionChanged -= value; }
        }

        #endregion

        #region Implementation of INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged
        {
            add { _items.PropertyChanged += value; }
            remove { _items.PropertyChanged -= value; }
        }

        #endregion

        #region Implementation of IDisposable

        /// <summary>
        ///     Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            _items.Dispose();
            _db.Dispose();
        }

        #endregion

        public override string ToString()
        {
            return string.Format("BaseXCollection on database {0} with {1} elements", _db.Name, Count);
        }

        public IPersistable GetPersistable(long id)
        {
            return Get(id).Item1;
        }

        public IVisualProperties GetVisualProperties(long id)
        {
            return Get(id).Item2;
        }

        public Tuple<IPersistable, IVisualProperties> GetPersistableAndVisualProperties(long id)
        {
            return Get(id);
        }

        //TODO: ins Interface bringen? auf jeden fall: kommentieren wann Fehler und wann null!!!
        public Tuple<IPersistable, IVisualProperties> GetPersistableAndVisualPropertiesIfExisting(long id)
        {
            using (_rwl.ReadLock())
            {
                return _items.SingleOrDefault(ip => ip.Id == id);
            }
        }

        public void Add(IPersistable p, IVisualProperties prop)
        {
            Add(Tuple.Create(p, prop));
        }
    }
}